//
// Unit tests for NetworkReachability
//
// Authors:
//	Marek Safar <msafar@xamarin.com>
//
// Copyright 2012 Xamarin Inc. All rights reserved.
//

#if !MONOMAC
using UIKit;
#endif
using SystemConfiguration;
using System.Net;
using System.Threading;
using System.Threading.Tasks;

namespace MonoTouchFixtures.SystemConfiguration {

	[TestFixture]
	[Preserve (AllMembers = true)]
	public class NetworkReachabilityTest {
		[Test]
		public void CtorNameAddress ()
		{
			using (var nr = new NetworkReachability ("apple.com")) {
				NetworkReachabilityFlags flags;

				Assert.IsTrue (nr.TryGetFlags (out flags));
				flags &= ~NetworkReachabilityFlags.TransientConnection; // Remove the TransientConnection flag if it's set
				Assert.That (flags, Is.EqualTo (NetworkReachabilityFlags.Reachable), "Reachable");
			}
		}

		[Test]
		public void CtorIPAddress ()
		{
			using (var nr = new NetworkReachability (IPAddress.Loopback)) {
				NetworkReachabilityFlags flags;

				Assert.IsTrue (nr.TryGetFlags (out flags), "#1");

				// inconsistent results across different iOS versions
				// < 9.0 -> Reachable | IsLocalAddress
				// 9.x -> Reachable | IsLocalAddress | IsDirect
				// 10.0 -> Reachable
				// so we're only checking the (most important) Reachable flag
				Assert.True ((flags & NetworkReachabilityFlags.Reachable) != 0, "Reachable");
			}

			using (var nr = new NetworkReachability (new IPAddress (new byte [] { 10, 99, 99, 99 }))) {
				NetworkReachabilityFlags flags;

				Assert.IsTrue (nr.TryGetFlags (out flags), "#2");
				//Assert.That (flags, Is.EqualTo (NetworkReachabilityFlags.Reachable), "#2 Reachable");
			}

			using (var nr = new NetworkReachability (IPAddress.IPv6Loopback)) {
				NetworkReachabilityFlags flags;

				Assert.IsTrue (nr.TryGetFlags (out flags), "#3");
				//Assert.That (flags, Is.EqualTo (
				//	NetworkReachabilityFlags.TransientConnection | NetworkReachabilityFlags.Reachable | NetworkReachabilityFlags.ConnectionRequired), "#3 Reachable");
			}

			using (var nr = new NetworkReachability (IPAddress.Parse ("2001:4860:4860::8844"))) {
				NetworkReachabilityFlags flags;

				Assert.IsTrue (nr.TryGetFlags (out flags), "#4");

				// TODO: Will probably change when IPv6 is enabled locally
				//Assert.That (flags, Is.EqualTo (
				//	NetworkReachabilityFlags.TransientConnection | NetworkReachabilityFlags.Reachable | NetworkReachabilityFlags.ConnectionRequired), "#4 Reachable");
			}

		}

		[Test]
		public void CtorIPAddressPair ()
		{
			var address = Dns.GetHostAddresses ("apple.com") [0];
			using (var nr = new NetworkReachability (IPAddress.Loopback, address)) {
				NetworkReachabilityFlags flags;

				Assert.IsTrue (nr.TryGetFlags (out flags), "#1");
				CheckLoopbackFlags (flags, "1", true);
			}

			using (var nr = new NetworkReachability (null, address)) {
				NetworkReachabilityFlags flags;

				Assert.IsTrue (nr.TryGetFlags (out flags), "#2");
				flags &= ~NetworkReachabilityFlags.TransientConnection; // Remove the TransientConnection flag if it's set
				Assert.That (flags, Is.EqualTo (NetworkReachabilityFlags.Reachable), "#2 Reachable");
			}

			using (var nr = new NetworkReachability (IPAddress.Loopback, null)) {
				NetworkReachabilityFlags flags;

				Assert.IsTrue (nr.TryGetFlags (out flags), "#3");
				CheckLoopbackFlags (flags, "3", false);
			}
		}

		void CheckLoopbackFlags (NetworkReachabilityFlags flags, string number, bool has_address)
		{
			var noFlags = (NetworkReachabilityFlags) 0;
			var otherFlags = (flags & ~(NetworkReachabilityFlags.Reachable | NetworkReachabilityFlags.IsLocalAddress | NetworkReachabilityFlags.IsDirect | NetworkReachabilityFlags.TransientConnection));

			// Different versions of OSes report different flags. Trying to
			// figure out which OS versions have which flags set turned out to
			// be a never-ending game of whack-a-mole, so just don't assert
			// that any specific flags are set.
			Assert.AreEqual (noFlags, otherFlags, $"#{number} No other flags: {flags.ToString ()}");
		}

		[Test]
		public void Ctor_Invalid ()
		{
			try {
				new NetworkReachability ((string) null);
				Assert.Fail ("#1");
			} catch (ArgumentNullException) {
			}

			try {
				new NetworkReachability ((IPAddress) null);
				Assert.Fail ("#2");
			} catch (ArgumentNullException) {
			}

			try {
				new NetworkReachability (null, null);
				Assert.Fail ("#3");
			} catch (ArgumentException) {
			}
		}

		[Test]
		public void Schedule ()
		{
			var ip = new IPAddress (0);
			using var defaultRouteReachability = new NetworkReachability (ip);
			Assert.IsTrue (defaultRouteReachability.Schedule (CFRunLoop.Main, CFRunLoop.ModeDefault), "Schedule");
			Assert.IsTrue (defaultRouteReachability.Unschedule (CFRunLoop.Main, CFRunLoop.ModeDefault), "Unschedule");
		}

		[Test]
		public void SetNotification ()
		{
			var ip = new IPAddress (0);
			using var reachability = new NetworkReachability (ip);

			// Test setting a notification
			var statusCode = reachability.SetNotification ((flags) => {
			});
			Assert.AreEqual (StatusCode.OK, statusCode, "SetNotification should succeed");

			// Test clearing the notification (this should free the GCHandle)
			statusCode = reachability.SetNotification (null);
			Assert.AreEqual (StatusCode.OK, statusCode, "SetNotification(null) should succeed");

			// Test setting notification again after clearing
			statusCode = reachability.SetNotification ((flags) => {
			});
			Assert.AreEqual (StatusCode.OK, statusCode, "SetNotification should succeed again");

			// Test that disposing also works (should free the GCHandle in Dispose)
		}

		[Test]
		public void SetNotification_GCHandleFreed ()
		{
			// Create weak references to track GC collection
			var weakRefs = new WeakReference [10];

			// Create NetworkReachability instances on a background thread
			var thread = new Thread (() => {
				for (int i = 0; i < 10; i++) {
					var ip = new IPAddress (0);
					var reachability = new NetworkReachability (ip);

					// Set a notification to allocate the GCHandle
					reachability.SetNotification ((flags) => {
					});

					// Store weak reference to track if object is collected
					weakRefs [i] = new WeakReference (reachability);

					// Dispose to ensure GCHandle is freed
					reachability.Dispose ();
				}
			});

			thread.Start ();
			thread.Join ();

			// Force garbage collection
			GC.Collect ();
			GC.WaitForPendingFinalizers ();
			GC.Collect ();

			// Assert that at least one NetworkReachability instance has been collected
			var collectedCount = 0;
			for (int i = 0; i < weakRefs.Length; i++) {
				if (!weakRefs [i].IsAlive) {
					collectedCount++;
				}
			}

			Assert.IsTrue (collectedCount > 0, $"Expected at least one NetworkReachability instance to be collected, but {collectedCount} were collected");
		}

		[Test]
		public void SetNotification_GCHandleFreedWithNull ()
		{
			// Create weak references to track GC collection
			var weakRefs = new WeakReference [10];

			// Create NetworkReachability instances on a background thread
			var thread = new Thread (() => {
				for (int i = 0; i < 10; i++) {
					var ip = new IPAddress (0);
					var reachability = new NetworkReachability (ip);

					// Set a notification to allocate the GCHandle
					reachability.SetNotification ((flags) => {
					});

					// Clear notification to free the GCHandle
					reachability.SetNotification (null);

					// Store weak reference to track if object is collected
					weakRefs [i] = new WeakReference (reachability);
				}
			});

			thread.Start ();
			thread.Join ();

			// Force garbage collection
			GC.Collect ();
			GC.WaitForPendingFinalizers ();
			GC.Collect ();

			// Assert that at least one NetworkReachability instance has been collected
			var collectedCount = 0;
			for (int i = 0; i < weakRefs.Length; i++) {
				if (!weakRefs [i].IsAlive) {
					collectedCount++;
				}
			}

			Assert.IsTrue (collectedCount > 0, $"Expected at least one NetworkReachability instance to be collected, but {collectedCount} were collected");
		}
	}
}
